package com.wtao.imagereader.aicamera;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.media.ImageReader;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;


import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executor;

public class CameraV2Proxy {
    private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
    private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
    private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
    private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();

    static {
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    static {
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);
    }

    public static final String TAG = "CameraV2Proxy";
    private CameraDevice mCameraDevice;
    private String mCameraId = "2";
    private Size mPreviewSize;
    private HandlerThread mCameraThread;
    private Handler mCameraHandler;
    private SurfaceTexture surfaceTexture;
    private Surface mSurface;
    private Surface mRecordSurface;
    private CaptureRequest.Builder mCaptureRequestBuilder;
    private CaptureRequest mCaptureRequest;
    private CameraCaptureSession mCameraCaptureSession;
    private ImageReader mImageReader;
    private ImageReader.OnImageAvailableListener mOnImageAvailableListener;
    private MediaRecorder mMediaRecorder;
    private Integer mSensorOrientation;
    private String mNextVideoAbsolutePath;
    private Size mVideoSize;
    private boolean mIsRecording;
    private Context mContext;
    private CameraStateCallback mCameraStateCallback;
    private FailedCallback failedCallback;

    public CameraV2Proxy(Context context) {
        mContext = context;
        startCameraThread();
    }

    public void setupCamera() {
        CameraManager cameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);

    }
    public void startCameraThread() {
        mCameraThread = new HandlerThread("CameraThread");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());
    }

    public boolean openCamera() {
        CameraManager cameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        try {
            if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }

            mVideoSize = new Size(1920, 1080);
            cameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);
            Log.d(TAG, "openCamera: " + mCameraId);
        } catch (Exception e) {
            Log.e(TAG, "openCamera: "+e.getMessage() );
            e.printStackTrace();
            if(failedCallback != null){
                failedCallback.failed();
            }
            return false;
        }
        return true;
    }

    private MediaRecorder createMediaRecorder(){
        MediaRecorder mediaRecorder = new MediaRecorder();
        try{
            setUpMediaRecorder(mediaRecorder);
        }catch (Exception e){
            e.printStackTrace();
        }
        if(mNextVideoAbsolutePath != null){
            File file = new File(mNextVideoAbsolutePath);
            if(file.exists() && file.length() == 0){
                file.delete();
            }
        }
        return mediaRecorder;
    }

    public CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            Log.e(TAG, "StateCallback onOpened: " );
            mCameraDevice = camera;
            if (null != mCameraStateCallback) {
                mCameraStateCallback.onOpened();
            }
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            Log.e(TAG, "StateCallback onDisconnected: " );
            camera.close();
            mCameraDevice = null;
            if(mCameraStateCallback != null){
                mCameraStateCallback.onClose();
            }
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            Log.e(TAG, "StateCallback onError: " +error);
            camera.close();
            mCameraDevice = null;
            if (null != mCameraStateCallback) {
                mCameraStateCallback.onClose();
                mCameraStateCallback.onError();
            }
        }
    };

    public static abstract class CameraStateCallback {
        public void onOpened() {}

        public void onError() {}

        public void onClose(){}
    }

    public void setCallback(CameraStateCallback callback) {
        mCameraStateCallback = callback;
    }

    public void setPreview(SurfaceTexture surface, ImageReader.OnImageAvailableListener listener) {
        surfaceTexture = surface;
        mOnImageAvailableListener = listener;
    }

    public SurfaceTexture getSurfaceTexture(){
        return surfaceTexture;
    }

    public void startPreview(CameraDevice cameraDevice) {
        Log.e(TAG, "startPreview(CameraDevice cameraDevice)");
        if(cameraDevice == null){
            Log.e(TAG, "cameraDevice null");
            return;
        }
        if(surfaceTexture == null){
            Log.e(TAG, "surfaceTexture null");
            return;
        }
        /*
        try {
            mRecordSurface = MediaCodec.createPersistentInputSurface();
            //mMediaRecorder = createMediaRecorder();
            mCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            Log.e(TAG, "previewWidth="+mPreviewSize.getWidth()+" previewHeight="+ mPreviewSize.getHeight());
            mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, 2);
            mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mCameraHandler);
            Surface imgReaderSurface = mImageReader.getSurface();
            assert surfaceTexture != null;
            surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            mSurface = new Surface(surfaceTexture);
            mCaptureRequestBuilder.addTarget(imgReaderSurface);
            mCaptureRequestBuilder.addTarget(mSurface);
            //mCaptureRequestBuilder.addTarget(mRecordSurface);
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);

            List<Surface> sessionSurfaces = new ArrayList<>();
            sessionSurfaces.add(imgReaderSurface);
            sessionSurfaces.add(mSurface);
            //sessionSurfaces.add(mRecordSurface);
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                List<OutputConfiguration> outputConfigurations = new ArrayList<>();
                for (Surface surface : sessionSurfaces) {
                    if (surface != null) {
                        OutputConfiguration configuration = new OutputConfiguration(surface);
                        outputConfigurations.add(configuration);
                    }
                }
                Log.e(TAG, "大于等于29");
                SessionConfiguration sessionConfiguration = new SessionConfiguration(SessionConfiguration.SESSION_REGULAR,
                        outputConfigurations, new HandlerExecutor(mCameraHandler), mSessionCreateCallback);
                sessionConfiguration.setSessionParameters(mCaptureRequestBuilder.build());
                cameraDevice.createCaptureSession(sessionConfiguration);
            } else {
                cameraDevice.createCaptureSession(sessionSurfaces, mSessionCreateCallback, mCameraHandler);
            }
            /*List<Surface> sessionSurfaces = new ArrayList<>();
            mSurface = new Surface(surfaceTexture);
            sessionSurfaces.add(mSurface);
            cameraDevice.createCaptureSession(sessionSurfaces, mSessionCreateCallback, mCameraHandler);

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }*/
        try {
            mSurface = new Surface(surfaceTexture);
            mCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mCaptureRequestBuilder.addTarget(mSurface);

            List<Surface> sessionSurfaces = new ArrayList<>();
            sessionSurfaces.add(mSurface);
            Log.e(TAG, "createCaptureSession");
            cameraDevice.createCaptureSession(sessionSurfaces, mSessionCreateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private CameraCaptureSession.StateCallback mSessionCreateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            try {
                Log.e(TAG, "mSessionCreateCallback onConfigured");
                mCameraCaptureSession = session;
                mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                mCaptureRequest = mCaptureRequestBuilder.build();
                //mCameraCaptureSession.setRepeatingRequest(mCaptureRequest, listener, mCameraHandler);
                mCameraCaptureSession.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
            Log.e(TAG, "mSessionCreateCallback onConfigureFailed");
            if(failedCallback != null){
                failedCallback.failed();
            }
        }
    };

    private CameraCaptureSession.CaptureCallback listener = new CameraCaptureSession.CaptureCallback() {
        int loss = 0;
        long loseTime = 0;
        @Override
        public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
            if (failedCallback != null && loss >= 10) {
                failedCallback.failed();
            }
        }

        @Override
        public void onCaptureBufferLost(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull Surface target, long frameNumber) {
            if(loseTime == 0){
                loseTime = System.currentTimeMillis();
            }else if(System.currentTimeMillis() - loseTime > 1000){
                loseTime = 0;
                loss = 0;
            }
            if (loss >= 10) {// 丢失5帧后，结束会话。
                try {
                    Log.e(TAG, "onCaptureBufferLost 丢失5帧后，结束会话");
                    session.abortCaptures();
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }
            loss++;
        }
    };

    public void setPreviewSize(int width, int height) {
        mPreviewSize = new Size(width, height);
    }

    public Handler getCameraHandler() {
        return mCameraHandler;
    }

    public CameraDevice getCameraDevice() {
        return mCameraDevice;
    }

    public int getOrientation() {
        try {
            CameraManager cameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(mCameraId);
            return characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return -1;
    }

    public void setCameraId(String cameraId) {
        this.mCameraId = cameraId;
    }

    //当前的摄像头
    public String getCurrentCameraId() {
        return mCameraId;
    }

    public void startRecorder() {
        if(mMediaRecorder == null) return;
        try {
            setUpMediaRecorder(mMediaRecorder);
            mMediaRecorder.start();
            mIsRecording = true;
            Log.i(TAG, "startRecorder...");
        } catch (IOException e) {
            Log.e(TAG, "startRecorder failed! " + e.getMessage());
        }
    }

    public void stopRecorder() {
        if (mMediaRecorder != null && mIsRecording) {
            Log.i(TAG, "stopRecorder...");
            mMediaRecorder.stop();
            mIsRecording = false;
        }
    }

    public void releaseCamera() {
        Log.e(TAG, "releaseRecorder closePreviewSession mCameraDevice.close()");
        releaseRecorder();
        closePreviewSession();
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
    }

    public void releaseRecorder() {
        if (mMediaRecorder == null) {
            return;
        }
        stopRecorder();  // stop if is recording
        mMediaRecorder.reset();
        mMediaRecorder.release();
        mMediaRecorder = null;
    }

    public void closePreviewSession() {
        Log.e(TAG, "closePreviewSession()");
        if (mCameraCaptureSession != null) {
            try {
                mCameraCaptureSession.stopRepeating();
                mCameraCaptureSession.abortCaptures();
                mCameraCaptureSession.close();
                mCameraCaptureSession = null;
                Log.e(TAG, "closePreviewSession() stopRepeating abortCaptures close");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void setUpMediaRecorder(MediaRecorder mediaRecorder) throws IOException {
        if (mediaRecorder == null) {
            Log.e(TAG, "configRecorder failed! mediaRecorder is null");
            return ;
        }
        mediaRecorder.reset();
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mediaRecorder.setVideoEncodingBitRate(8 * 1920*1080);
        // Set video frame capture rate
        mediaRecorder.setCaptureRate(30);
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
        // Sets the frame rate of the video to be captured
        mediaRecorder.setVideoFrameRate(30);
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        //mediaRecorder.setInputSurface(mRecordSurface);
        String fileName = new SimpleDateFormat("yyyy-MM-dd_hhmmss_SSS").format(new Date()) + ".mp4";
        String outputPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pictures/", fileName).getAbsolutePath();
        File parentFile = new File(outputPath).getParentFile();
        if (!parentFile.exists()) {
            boolean isSuccess = parentFile.mkdirs();
            Log.e(TAG, "创建目录isSuccess=" + isSuccess);
        }
        Log.e(TAG, "outputPath=" + outputPath + " parentFile.isDir=" + parentFile.isDirectory());
        mNextVideoAbsolutePath = outputPath;
        mediaRecorder.setOutputFile(mNextVideoAbsolutePath);
        mediaRecorder.prepare();
        mIsRecording = false;
    }

    //给外界调用获取MediaRecorder录制视频的保存路径
    public String getVideoSavePath() {
        return mNextVideoAbsolutePath;
    }

    public boolean isRecording() {
        return mIsRecording;
    }

    public void startOrStopRecord() {
        if (mIsRecording) {
            stopRecorder();
        } else {
            startRecorder();
        }
    }

    private class HandlerExecutor implements Executor {
        private final Handler ihandler;

        public HandlerExecutor(Handler handler) {
            ihandler = handler;
        }

        @Override
        public void execute(Runnable runCmd) {
            ihandler.post(runCmd);
        }
    }

    public void setFailedCallback(FailedCallback failedCallback) {
        this.failedCallback = failedCallback;
    }

    public interface FailedCallback {
        public void failed();
    }
}

